#pragma once
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <string>

#include <pthread.h>

#include <cstring>
#include "log.hpp"

using namespace std;

logger lg;


class client {
private:
    static const size_t default_buffer_size = 1024;
public:
    client(uint16_t port, const string& server_ip) :_server_port(port), _server_ip(server_ip) {
        lg.setScreenPrint();

        // 要通信就得先建立套接字文件
        _sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sock_fd < 0) {
            lg(FATAL, "socket error %s", strerror(errno));
            exit(errno);
        }
        // 客户端需要bind吗？
        // 要，不然服务器怎么找到客户端呢？
        // 但是不需要用户显式地绑定，一般由操作系统随机选择
        // 为什么不需要用户显式绑定呢？
        // 首先要明确一个端口号只能由一个进程所绑定
        // 假设允许客户端手动绑定的话，如果多个客户端程序都绑定同一个端口号
        // 这就会导致其它客户端程序无法正常启动，这是不允许发生的
        // 所以要由操作系统自由选择一个目前没有被占用的端口号进行绑定
        // 这通常是通过将套接字地址结构体中的端口号设置为 0 来实现的
        // 其实客户端端口号是多少并不重要，只要保证唯一性就可以了
        
        // 系统是在客户端首次发送数据的时候进行动态绑定
        // 所以创建完套接字直接就可以发送数据了
    }

    static void *send(void *args) {
        client *ptr = static_cast<client *>(args);
        // 通过用户提供的服务器套接字地址信息进行填充
        struct sockaddr_in server_addr;
        server_addr.sin_family = AF_INET;
        // 在网络上传输就得转换成网络字节序列
        server_addr.sin_addr.s_addr = inet_addr(ptr->_server_ip.c_str());
        server_addr.sin_port = htons(ptr->_server_port);
        socklen_t len = sizeof(server_addr);
        
        while(true) {
            // 向服务器发送请求
            cout << "[client say]# ";
            string msg;
            getline(cin, msg);
            ssize_t n = sendto(ptr->_sock_fd, msg.c_str(), msg.size(), 0, (const struct sockaddr *)&server_addr, len);
            if (n < 0) {
                lg(FATAL, "sendto error %s", strerror(errno));
                exit(errno);
            }
        }
        return nullptr;
    }   

    static void *recv(void *args) {
        client *ptr = static_cast<client *>(args);
        while(true) {
            // 接收服务器的响应
            // 数据可能会发生给不同的服务器，所以在接收时就需要用一个ipv4协议的套接字类型结构体来接收目标服务器的地址数据
            // 只不过在当前代码中只会收到一个服务器的响应，所以该结构体中填充的数据与发送函数中填充的结构体相同
            struct sockaddr_in tar_addr;
            socklen_t tar_len = sizeof(tar_addr);
            char out_buffer[default_buffer_size];
            ssize_t n = recvfrom(ptr->_sock_fd, out_buffer, default_buffer_size - 1, 0,  (struct sockaddr *)&tar_addr, &tar_len);
            if(n == -1) {
                lg(FATAL, "recvfrom error %s", strerror(errno));
                exit(errno);
            }
            // 展示服务器的响应
            out_buffer[n] = '\0';
            string o = out_buffer;
            cout << o << endl;
        }
        return nullptr;
    }
    
    void run() {
        pthread_t send_t, recv_t;
        pthread_create(&send_t, nullptr, send, this);
        pthread_create(&recv_t, nullptr, recv, this);
        
        pthread_join(send_t, nullptr);
        pthread_join(recv_t, nullptr);
    }

private:
    int _sock_fd;
    //目标服务器端口和IP地址
    uint16_t _server_port;
    string _server_ip;
};